/*
 * @Author: 王汝鹏
 * @Date: 2021-01-05 17:32:45
 * @LastEditTime: 2022-03-10 11:17:55
 * @LastEditors: Please set LastEditors
 * @Description: 生成随机块数据程序
 * @FilePath: \mock_-device_-data\test4.js
 */

const { outputJsonSync } = require("fs-extra");

/**
 * 生成初始画板
 * @param {Number} width 宽度(像素)
 * @param {Number} height 高度(像素)
 * @param {Any} initial_val 默认值
 * @return {Array<Array<Any>>} 初始画板
 */
const make_drawing_board = function(width, height, initial_val) {
  let board = new Array(height);
  for (let j=0; j<height; j++) {
    board[j] = new Array(width);
    board[j].fill(initial_val);
  }

  return board;
}

/**
 * 生成随机块图像
 * @param {Number} width 宽度(像素)
 * @param {Number} height 高度(像素)
 * @param {Number} points 点数
 * @return {Array<Array<Number>>} 块图像,1代表点有值,0代表点无值
 */
const make_random_block = function(width, height, points) {
  //  创建块的画板
  let block_board = new Array(height);
  for (let r=0; r<height; r++) {
    block_board[r] = new Array(width);
    block_board[r].fill(0);
  }

  for (let i=0; i<points; i++) {
    //  随机生成点坐标(x,y)
    let x = Math.floor(Math.random()*width);
    let y = Math.floor(Math.random()*height);
    
    //  过滤点坐标(使块边看起来更平滑), 以图像中心点做圆, 对圆外的点进行舍弃
    //  计算y坐标对应的弧长
    //  a² + b² = c² => (弧长一半)² + (弧线到远点距离一半)² = (半径)²
    const R = Math.max(width,height) / 2;
    let c = R;
    let b = Math.abs(R-y);
    let a = Math.sqrt(c*c - b*b);

    //  计算弧线的x起始坐标, 在此范围内的点为圆内的点, 范围外的点进行舍弃
    let x_start = Math.round(R - a);
    let x_end   = Math.round(R + a);

    //  保留圆内的点,舍弃圆外的点
    (x>=x_start && x<=x_end) 
      ? (block_board[y][x] = 1)
      : (i -= 1);
  }

  //  绘制完成后随机生成中心点, 将所有点向中心点聚集
  // let center_x = Math.floor(Math.random()*width);
  // let center_y = Math.floor(Math.random()*height);
  let center_x = Math.floor(width/2);
  let center_y = Math.floor(height/2);

  //  以x轴为中心聚集
  const _x_shrink = function() {
    for (let c=0; c<width; c++) {
      //  聚集x轴上半部分的点
      let upper_half = 0;
      for (let r=center_y-1; r>=0; r--) {
        if (block_board[r][c] == 1)
          upper_half += 1;
      }
      for (let r=center_y-1; r>=0; r--) {
        block_board[r][c] = ((upper_half--) > 0) ? 1 : 0;
      }

      //  聚集x轴下半部分的点
      let lower_half = 0;
      for (let r=center_y; r<height; r++) {
        if (block_board[r][c] == 1)
          lower_half += 1;
      }
      for (let r=center_y; r<height; r++) {
        block_board[r][c] = ((lower_half--) > 0) ? 1 : 0;
      }
    }
  };
  //  以y轴为中心聚集
  const _y_shrink = function() {
    for (let r=0; r<height; r++) {
      //  聚集y轴左半部分的点
      let left_half = 0;
      for (let c=center_x-1; c>=0; c--) {
        if (block_board[r][c] == 1)
          left_half += 1;
      }
      for (let c=center_x-1; c>=0; c--) {
        block_board[r][c] = ((left_half--) > 0) ? 1 : 0;
      }

      //  聚集y轴右半部分的点
      let right_half = 0;
      for (let c=center_x; c<height; c++) {
        if (block_board[r][c] == 1)
          right_half += 1;
      }
      for (let c=center_x; c<height; c++) {
        block_board[r][c] = ((right_half--) > 0) ? 1 : 0;
      }
    }
  };
  //  以中心点进行聚集
  const _center_shrink = function() {
    for (let r=0; r<height; r++) {
      for (let c=0; c<width; c++) { 
        //  当前点有值, 循环向内判断是否有其他空位需要补
        if (block_board[r][c] == 1) {
          //  需要向内比较的次数
          let compare_count = Math.abs(center_x-c) * Math.abs(center_y-r);
          for (let i=compare_count-1; i>=0; i--) {
            let compare_point_x = c + Math.round((center_x-c)/compare_count*i);
            let compare_point_y = r + Math.round((center_y-r)/compare_count*i);

            let compare_point = block_board[compare_point_y][compare_point_x];
            //  ==0说明内部有没有填充的点,需要将当前点填充到内部该位置
            if (compare_point == 0) {
              block_board[compare_point_y][compare_point_x] = 1;
              block_board[r][c] = 0;
              break;
            }
          }
/********* 分析过程
          //  假设当前点为:1,3 中心点为:5,5
          //  5-1=4, 5-3=2
          //  则需要判断的点为8个 分别为
          for (let i=0; i<8; i++) {
            //  i=0 => x=1+(4/8*0), y=3+(2/8*0) => 1, 3
            //  i=1 => x=1+(4/8*1), y=3+(2/8*1) => 2, 3
            //  i=2 => x=1+(4/8*2), y=3+(2/8*2) => 2, 4
            //  i=3 => x=1+(4/8*3), y=3+(2/8*3) => 3, 4
            //  i=4 => x=1+(4/8*4), y=3+(2/8*4) => 3, 4
            //  i=5 => x=1+(4/8*5), y=3+(2/8*5) => 4, 4
            //  i=6 => x=1+(4/8*6), y=3+(2/8*6) => 4, 5
            //  i=7 => x=1+(4/8*7), y=3+(2/8*7) => 5, 5
          }
**/
        }
      }
    }
  }
  //  块图像去毛边
  const _burring = function() {
    //  上边毛刺
    for (let i=0; i<center_y; i++) {
      let point_count = 0;
      for (let j=0; j<width; j++) {
        if (block_board[i][j] == 1) 
          point_count += 1;
      }
      if (point_count > 5) 
        break;

      for (let j=0; j<width; j++) {
        block_board[i][j] = 0;
      }
    }

    //  下边毛刺
    for (let i=height-1; i>center_y; i--) {
      let point_count = 0;
      for (let j=0; j<width; j++) {
        if (block_board[i][j] == 1) 
          point_count += 1;
      }
      if (point_count > 5) 
        break;

      for (let j=0; j<width; j++) {
        block_board[i][j] = 0;
      }
    }

    //  左边毛刺
    for (let i=0; i<center_x; i++) {
      let point_count = 0;
      for (let j=0; j<height; j++) {
        if (block_board[j][i] == 1) 
          point_count += 1;
      }
      if (point_count > 5) 
        break;

      for (let j=0; j<height; j++) {
        block_board[j][i] = 0;
      }
    }

    //  右边毛刺
    for (let i=width-1; i>center_x; i--) {
      let point_count = 0;
      for (let j=0; j<height; j++) {
        if (block_board[j][i] == 1) 
          point_count += 1;
      }
      if (point_count > 5) 
        break;

      for (let j=0; j<height; j++) {
        block_board[j][i] = 0;
      }
    }
  }

  _center_shrink();

  //  收缩的模式, 0:先以x轴为中心聚集, 1:先以y轴为中心聚集
  let model = Math.round(Math.random()*1);
  if (model == 0) {
    _x_shrink();
    _y_shrink();
  } else {
    _y_shrink();
    _x_shrink();
  }

  _burring();
  return block_board;
}

/**
 * 绘制块图像
 * @param {Array<Array<Any>>} board 画板
 * @param {Array<Array<Number>>} block 块数据
 * @param {Number} x 块起始点x坐标
 * @param {Number} y 块起始点y坐标
 * @param {String} type 矿石类型 "coal":煤炭, "rock":矸石
 */
const draw_block = function(board, block, x, y, type) {
  let board_width  = board[0].length;
  let board_height = board.length;
  let block_width  = block[0].length;
  let block_height = block.length;

  for (let r=0; r<block_height; r++) {
    for (let c=0; c<block_width; c++) {
      //  点有数据才绘制, (==1代表有数据)
      if (block[r][c] == 1) {
        //  坐标在画板之外的点数据舍弃
        if ((x+c<0) || (x+c>=board_width))  continue;
        if ((y+r<0) || (y+r>=board_height)) continue;

        // type=="coal" 煤炭,绘制颜色深一些,10000~20000
        // type=="rock" 矸石,绘制颜色浅一些,20000~30000
        let value = Math.round(Math.random()*10000) + (type=="coal"?10000:20000);
        board[y+r][x+c] = value;
      }
    }
  }
}

/**
 * 随机分配块数据的绘制点
 * @param {Array<Array<Any>>} board 画板
 * @param {Array<Array<Number>>} block 块数据
 * @param {Number} try_count 尝试分配次数
 * @return {Object} 绘制位置坐标
 * ```{ x:number, y:number }```
 */
const get_draw_point = function(board, block, try_count) {
  let board_width  = board[0].length;
  let board_height = board.length;
  let block_width  = block[0].length;
  let block_height = block.length;

  //  生成随机绘制起始点(x, y), x:(0-块一半宽)~(画板宽+块一半宽), y:(0-块一半高)~(画板高+块一半高)
  let x = Math.floor(Math.random()*(board_width+block_width)-(block_width/2));
  let y = Math.floor(Math.random()*(board_height+block_height)-(block_height/2));
  for (let i=0; i<try_count; i++) {
    //  比较循环次数
    let compare_count  = block_width * block_height;
    //  比较结果,false:点不能用,true:点可用
    let compare_result = true;

    for (let k=0; k<compare_count; k++) {
      let compare_point_x = x + Math.round(block_width/compare_count*k);
      let compare_point_y = y + Math.round(block_height/compare_count*k);
      if (compare_point_x<0 || compare_point_x>=board_width)  continue;
      if (compare_point_y<0 || compare_point_y>=board_height) continue;

      let compare_point = board[compare_point_y][compare_point_x];
      if (compare_point != 52000) {
        compare_result = false;
        break;
      }
    }

    //  绘制位置可用
    if (compare_result == true) break;

    //  不可用,重新分配点坐标
    x = Math.floor(Math.random()*(board_width+block_width)-(block_width/2));
    y = Math.floor(Math.random()*(board_height+block_height)-(block_height/2));
  }

  return { x, y };
}

const ROW_LENGTH    = 512;
const COLUMN_LENGTH = 448;
//  生成10个文件
for (let f=0; f<10; f++) {
  let board = make_drawing_board(COLUMN_LENGTH, ROW_LENGTH, 52000);

  //  绘制250个块数据
  for (let i=0; i<250; i++) {
    //  宽度在 75 ~ 130 之间
    const BlockWidth  = Math.round((Math.random()*15)+35);
    //  高度在 宽度-5 ~ 宽度+5 之间
    // const BlockHeight = BlockWidth + Math.round((Math.random()*20)-(Math.random()*10));
    // const BlockHeight = BlockWidth;
    const BlockHeight = Math.round((Math.random()*15)+35);

    //  绘制的点数量为块面积2/3个点, 但可能会有重复的点
    const BlockPoints = Math.floor(Math.random()*BlockWidth*BlockHeight*(2/3));

    //  获取块数据
    let block = make_random_block(BlockWidth, BlockHeight, BlockPoints);

    //  随机获取块的绘制位置
    let { x, y } = get_draw_point(board, block, 10);
    //  绘制块数据到画板(x,y)位置, 绘制(煤炭或矸石)颜色 
    let block_type = (Math.random()*2>1) ? "rock" : "coal";
    draw_block(board, block, x, y, block_type);
  }

  //  转换每行数据
  let out_data = [];
  let id       = 0;
  let temp_id  = 0;

  for (let r=0; r<ROW_LENGTH; r++) {
    out_data.push({
      id, temp_id,
      LE: board[r],
      HE: board[r].map(i => {
        if (i>=52000) { return 52000; }
        let r = i>20000 ? 1 : 2; 
        let LE_value = i;
        let HE_value = Math.round(65535*0.8 - ((65535*0.8) * Math.exp(Math.log((65535*0.8-LE_value)/(65535*0.8)) / r)));
        return HE_value;
      })
    });
    
    id      += 1;
    temp_id += 1;
  }

  //  输出文件
  outputJsonSync(`./data_2/${f+1}.data`, out_data);
}


// 50%喷吹 将煤炭设置为颜色比较深10000左右, 矸石设置为颜色亮一些20000左右
// 使用公式计算出对应的HE值.

